/******************************************************************************

    Simple Authentication and Authorisation System (SAAS)

    conf.c

    Copyright (C) 2011  Josh Goes


    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    If you require a mailing address for this software, please send your
    mail to the following address:
        SAAS
        GPO Box 2973
        BRISBANE QLD 4001
        AUSTRALIA

******************************************************************************/


#include "saas-build.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>



// TODO: this function is a bit of a mess. perhaps there's an easier way to do what it does.
void _conf_parse_hosts( saas_t *inst , char *hosts )
{
	// run through all the hosts given, and update the inst->hosts and
	// inst->num_hosts as appropriate.

	uint16_t port_num;
	uint32_t  timeout_val;
	int len = strlen( hosts ), idx = 0;
	char *host, *offset, *port, *timeout, *post_ip, *tmp, *tmp2;
	offset = hosts;

	// run through each character and at the end of each host, process it.
	host = NULL;
	for ( ; len >= 0 ; len-- )
	{
		if ( *offset != ' ' && host == NULL )
			host = offset;
		else if ( ( *offset == ' ' || *offset == '\t' || *offset == '\0' ) && host != NULL )
		{
			// we've just captured a host. put a null-terminator at the end
			// of the host so that our str* commands are safe.
			*offset = '\0';

			// see if a port was specified, but make sure not to catch IPv6
			// addresses.
			if ( *host == '[' )
			{
				// yeah there's an IPv6 addy here... so skip to the end of the
				// IPv6 addy and see from there.
				post_ip = strchr( host , ']' );
				if ( post_ip == NULL )
				{
					// syntax error. no closing square bracket!!
					logm( inst , LOG_WARNING , "Syntax error in saas.conf: IPv6 address `%s' missing `]' bracket" , host );
					offset++;
					continue;
				}
				port    = strchr( post_ip , ':' );
				timeout = strchr( post_ip , '/' );
			}
			else
			{
				// so it's IPv4 or DNS... no need to handle colons with care (is what he said)
				port    = strchr( host , ':' );
				timeout = strchr( host , '/' );
			}
			if ( port != NULL )
			{
				// change the colon into a null-terminator, so we can safely capture the host IP/name below.
				*port = '\0';
				port++;

				// now we see what comes first, the '/' character, or the end of the line.
				tmp  = strchr( port , '/' );
				tmp2 = strchr( port , '\0' );
				if ( tmp != NULL && tmp < tmp2 )
				{
					// the '/' character comes before the newline... so change the '/' into
					// a null terminator and capture the port.
					*tmp = '\0';
				}
				port_num = atoi( port );
			}
			else
			{
				port_num = SAAS_DEFAULT_PORT;
			}

			// now we see if there's a timeout set.
			if ( timeout != NULL )
			{
				// the timeouts are the last appendages on a host line... so it should be easy to pull out.
				// the 'timeout' pointer we were given is at the '/' or '\0' character, so shuffle it forward one byte.
				*timeout = '\0';
				timeout++;
				timeout_val = atoi( timeout );
			}
			else
			{
				timeout_val = 0;
			}

			// if there's a close bracket symbol, remove it.
			tmp = strchr( host , ']' );
			if ( tmp != NULL )
				*tmp = '\0';

			// determine what type the "host" is.
			if ( *host == '[' && is_ipv6( host + 1 ) == SAAS_OP_SUCCESS )
			{
				// an IPv6 IP address.
				host++;
				copy_ipv6_addr( inst->hosts[idx].ipv6_addr , host );
				inst->hosts[idx].port    = port_num;
				inst->hosts[idx].timeout = timeout_val;
				inst->hosts[idx].type    = SAAS_HOST_TYPE_IPV6;
				idx++;
			}
			else if ( is_ipv4( host ) == SAAS_OP_SUCCESS )
			{
				// an IPv4 IP address.
				copy_ipv4_addr( inst->hosts[idx].ipv4_addr , host );
				inst->hosts[idx].port    = port_num;
				inst->hosts[idx].timeout = timeout_val;
				inst->hosts[idx].type    = SAAS_HOST_TYPE_IPV4;
				idx++;
			}
			else
			{
				// a DNS record.
				strcpy( (char *) &inst->hosts[idx].dns_name , host );
				inst->hosts[idx].port    = port_num;
				inst->hosts[idx].timeout = timeout_val;
				inst->hosts[idx].type    = SAAS_HOST_TYPE_DNS;
				idx++;
			}

			// we've processed a host now.
			host = NULL;
		}
		offset++;
	}
	inst->num_hosts = idx;
}



// TODO: rebuild this function. it's just been hacked together and is pretty
//       damn ugly. it does the job.. but ugh.. I'd rather look at dog shit.
uint16_t _conf_parse( saas_t *inst , const char *file )
{
	struct stat st;
	char *offset, *line_start, *line_end, *data, *key, *value, *find;
	int line_len = 0;

	// find out how big the file is.
	logm( inst , LOG_DEBUG , "parsing config file" );
	if ( stat( file , &st ) == -1 )
	{
		logm( inst , LOG_ERR , "error finding configuration file: %d (%s)" , errno , strerror( errno ) );
		return SAAS_OP_FILE_ERROR;
	}

	// if it's too big in size, ignore it.
	if ( st.st_size >= 1048576 )
	{
		logm( inst , LOG_ERR , "config file is too big (>=1MB)" );
		return SAAS_OP_FILE_ERROR;
	}

	// open the file.
	int fd = open( file , O_RDONLY );
	if ( fd == -1 )
	{
		logm( inst , LOG_ERR , "error opening config file: (%d) %s" , errno , strerror( errno ) );
		return SAAS_OP_FILE_ERROR;
	}

	// suck down the entire file into memory.
	data = malloc( st.st_size );
	if ( data == NULL )
	{
		logm( inst , LOG_ERR , "error allocating memory for config data" );
		return SAAS_OP_MEM_ERROR;
	}
	read( fd , data , st.st_size );
	close( fd );

	// parse through the config line by line.
	offset = data;
	uint32_t  line = 0;
	while ( offset < ( data + st.st_size ) )
	{
		// figure out the bounds of the line.
		line++;
		line_start = offset;
		line_end   = strchr( offset , '\n' );
		if ( line_end == NULL )
			line_end = ( data + st.st_size );
		line_len = line_end - line_start;
		offset = line_end + 1;
		*line_end = '\0';

		// run through the line removing comments.
		find = strchr( line_start , '#' );
		if ( find != NULL && find < line_end )
			memset( find , '\0' , line_end - find );

		// run through the line converting tabs into spaces.
		while ( ( find = strchr( line_start , '\t' ) ) != NULL )
			*find = ' ';

		// if there's no = sign then skip forward onto the next line.
		find = strchr( line_start , '=' );
		if ( find == NULL || find >= line_end )
			continue;
		*find = '\0';

		// pull out the key and value.
		key = line_start;
		value = find + 1;
		trim( &key );
		trim( &value );

		if      ( strcmp( key , "host_order" ) == 0 )
			inst->host_order = atoi( value );
		else if ( strcmp( key , "dns_prefer_ipv4" ) == 0 )
			inst->dns_prefer_ipv4 = atoi( value );
		else if ( strcmp( key , "dns_prefer_ipv6" ) == 0 )
			inst->dns_prefer_ipv6 = atoi( value );
		else if ( strcmp( key , "use_ipv4" ) == 0 )
			inst->use_ipv4 = atoi( value );
		else if ( strcmp( key , "use_ipv6" ) == 0 )
			inst->use_ipv6 = atoi( value );
		else if ( strcmp( key , "enc_type" ) == 0 )
		{
			if ( _conf_parse_enc_type( inst , value ) != SAAS_OP_SUCCESS )
			{
				// error parsing the enc_type. abort config parsing.
				free( data );
				logm( inst , LOG_ERR , "`enc_type' field in config file contains invalid value" );
				return SAAS_OP_PARSE_ERROR;
			}
		}
		else if ( strcmp( key , "timeout" ) == 0 )
			inst->timeout = atoi( value );
		else if ( strcmp( key , "time_adjust" ) == 0 )
			inst->time_adjust = atoi( value );
		else if ( strcmp( key , "dns_try_all_ips" ) == 0 )
			inst->dns_try_all_ips = atoi( value );
		else if ( strcmp( key , "key_time_tolerance" ) == 0 )
			inst->key_time_tolerance = atoi( value );
		else if ( strcmp( key , "key_time_type" ) == 0 )
		{
			if ( strcmp( value , "min" ) == 0 )
				inst->key_time_type = SAAS_KEY_TIME_TYPE_MIN;
			else if ( strcmp( value , "sec" ) == 0 )
				inst->key_time_type = SAAS_KEY_TIME_TYPE_SEC;
			else
			{
				free( data );
				logm( inst , LOG_ERR , "`key_time_type' field in config file contains invalid value" );
				return SAAS_OP_PARSE_ERROR;
			}
		}
		else if ( strcmp( key , "password_salt" ) == 0 )
		{
			if ( strlen( value ) <= 255 )
			{
				strcpy( (char *) inst->pw_salt , value );
				inst->pw_salt_len = strlen( value );
			}
			else
			{
				logm( inst , LOG_ERR , "`password_salt' field too long" );
			}
		}
		else if ( strcmp( key , "secret" ) == 0 )
		{
			strncpy( (char *) inst->secret , value , SAAS_MAX_SECRET_LEN );
			if ( strlen( value ) >= SAAS_MAX_SECRET_LEN )
			{
				inst->secret[SAAS_MAX_SECRET_LEN-1] = '\0';
				logm( inst , LOG_ERR , "`secret' field too long" );
			}
			inst->secret_len = strlen( (char *) inst->secret );
		}
		else if ( strcmp( key , "hosts" ) == 0 )
		{
			_conf_parse_hosts( inst , value );
		}
		else
		{
			logm( inst , LOG_ERR , "error: unknown field `%s'" , key );
			return SAAS_OP_PARSE_ERROR;
		}

	}
	free( data );

	return SAAS_OP_SUCCESS;
}



